#pragma once

#include <stdint.h>
#include <stdio.h>
#include <pthread.h>

static char VENDOR[] = "Bacon Enterprises";

#define MAX_PROGSZ 16384
#define MAX_INTERRUPTS 255
#define MAX_LOG 128
#define MAX_MEM 64
#define MAX_HWMEM 163232
#define MAX_DEV 16
#define MAX_JMP 16
#define REGS 9
#define BREGS 2   //Number of register banks
#define CORES 0

#define MEM_BITS (MAX_MEM * 16)
#define MEM_BYTES (MEM_BITS / 8)
#define HWMEM_BITS (MAX_HWMEM * 16)
#define HWMEM_BYTES (HWMEM_BITS / 8)
#define HWMEM_START (MAX_MEM+1)

#define CHECKBIT(field, bit) (field & (1 << bit))
#define SETBIT(field, bit) field |= (1 << bit)

#define bitmask(a)  ((1 << a++) - 1)

#define logs(fmt,...)              \
    if (getenv("DEBUG")) {	   \
    printf(fmt, ##__VA_ARGS__);    \
    putc('\n',stdout);             \
    }

#define GT 0
#define LT 1
#define EQ 2

#define HALT 0x0
#define ADD  0x1
#define SUB  0x2
#define MLT  0x3
#define DIV  0x4
#define JMPS 0x5
#define LDI  0x6
#define LDR  0x7
#define LDM  0x8
#define DBG  0x9
#define AND  0xA
#define OR   0xB
#define XOR  0xC
#define NOT  0xD
#define RST  0xE
#define INIT 0xF
#define ST16 0x10
#define LOOP 0x11
#define JMP  0x12
#define CRR  0x13
#define CRI  0x14
#define CRM  0x15
#define JGT  0x16
#define JLT  0x17
#define JEQ  0x18
#define NOP  0x19
#define MOD  0x1A
#define JGTS 0x1B
#define JLTS 0x1C
#define JEQS 0x1D
#define JGE  0x1E
#define JLE  0x1F
#define JGES 0x20
#define JLES 0x21
#define SHL  0x22
#define SHR  0x23
#define SHLI 0x24
#define SHRI 0x25
#define ST8  0x26
#define INT  0x27
#define LDP  0x28
#define INC  0x29
#define DEC  0x2A

#define INSTR_BYTES (sizeof(instruction))

typedef enum bool {TRUE=1, FALSE=0} bool;

typedef bool (*cpu_t)();

typedef struct instruction48 {
  int8_t opcode;
  int8_t reg1;
  int8_t reg2;
  int8_t reg3;
  int16_t val;
} instruction;

typedef struct registers_t {
  int16_t regs[REGS];  //General purpose registers
  char* pc;            //Pointer to current instruction
  uint16_t cst;        //Status register
  char* sp;            //Stack pointer
  int16_t slr;         //Subroutine Link Register
  char* isp;           //Interrupt Stack Pointer
  char* vtp;           //Vector Table Pointer
} registers_t;         //Register file
/* Quick note on status register:
   0000-0000-0000-0000
    D    C    B    A
   Little-Endian
   A sets CPU mode (normal = 0000, system = 0001,
   interrupt = 0010)
   B is for which register bank was last saved to (this is
   recurssive, so if a new set of registers gets saved, then this
   gets saved)
   C is for in use register banks, done in the same way as Unix
   file permissions, so first bank is 1, second is two, third is
   four
   D is currently unused
*/

typedef struct core_t {
  char id;              //Core ID
  char* vendor_id;      //Professional!
  cpu_t loop;           //Main cpu loop
  pthread_t tid;        //ID for pthread
  registers_t reg;      //Primary registers
  registers_t breg[BREGS]; //Register banks
  bool interrupt_lock;  //We're going with non-nested interrupts
  int state;            //Set to 1 whilst running, 0 otherwise
} core_t;

typedef struct hardwareObject {
  int16_t port;
  int8_t memtype; //8 or 16
  void* object;
  void* dl;
} hardwareObject;

typedef struct hardwareObject8 {
  size_t memory_sz;        //Size of memory region to allocate for device in bytes
  bool (*attach)();        //Attach the hardware device at boot. Register interrupts or otherwise prepare the device here.
  bool (*detach)();        //Detach the hardware device. Unregister interrupts, deallocate memory, etc.
  bool (*recv8)(int8_t);   //Receive 8 bits of data
  bool (*recv16)(int16_t); //Receive 16 bits of data
  void (*sleep)();         //Put device to sleep
  void (*wake)();          //Wake device
  int16_t memory_start;    //Pointer to beginning of device memory (set by the machine before attach)
  int16_t memory_end;      //Pointer to end of device memory (set by the machine before attach)
  bool active;             //Power status
} hardwareObject8;

void _halt(); void _ldm();
void _add();  void _dbg();
void _sub();  void _and();
void _mlt();  void _or();
void _div();  void _xor();
void _jmp();  void _not();
void _ldi();  void _rst();
void _ldr();  void _init();
void _stm();  void _loop();
void _jmp();  void _crr();
void _cri();  void _crm();
void _jgt();  void _jlt();
void _jeq();  void _rnd();
void _mod();  void _jgts();
void _jlts(); void _jeqs();
void _jges(); void _jle();
void _jges(); void _jles();
void _shl();  void _shr();
void _shli(); void _shri();

int16_t memget(int16_t addr, uint_fast8_t bits);
bool memstore(int16_t addr, int16_t value, uint_fast8_t bits);
void reg_load(core_t *core, uint16_t bank);
void reg_save(core_t *core);
void hwerr(char* reason);
void interrupt(int16_t i, core_t *core);
void regivt(int16_t interrupt, bool (*func)());

void run_cpu(core_t *core);
int start_vm(char* argv[]);
